package com.huiyeji.idempotent;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author zhaoduo
 */
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private RedisInterface redisInterface;

    @Around("@annotation(com.huiyeji.idempotent.Idempotent)")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Idempotent annotation = signature.getMethod().getAnnotation(Idempotent.class);
        String idempotentKey = "mdm:idempotent:" + getIdempotentKey(point,signature,annotation);
        String value = redisInterface.get(idempotentKey);
        if (value == null || "".equals(value)){
            redisInterface.set(idempotentKey,"true",annotation.expireSeconds());
            try {
                return point.proceed();
            } finally {
                redisInterface.del(idempotentKey);
            }
        } else {
            throw generateException(annotation,null);
        }
    }

    private Throwable generateException(Idempotent annotation,String message) throws Exception{
        Class<? extends Throwable> exceptionType = annotation.exceptionType();
        return exceptionType.getConstructor(String.class).newInstance(message!=null?message:annotation.exceptionMessage());
    }

    private String getIdempotentKey(ProceedingJoinPoint point, MethodSignature signature, Idempotent annotation) throws Throwable {
        String group = "".equals(annotation.group()) ? signature.getName() : annotation.group();
        Class<? extends KeyGenerator> configClass = annotation.keyGenerator();
        KeyGenerator config = configClass.getConstructor().newInstance();
        try {
            String configIdempotentKey = config.getIdempotentKey(new ParamContext(point.getArgs(), signature.getParameterNames()));
            return group + ":" + configIdempotentKey;
        } catch (Exception exception){
            throw generateException(annotation,"Idempotent Generate Key Error : " + exception.getMessage());
        }
    }
}
